#pragma once
#include "CGIAccess.h"
#include "CGIPrefix.h"
#include "ClearValue.h"
#include "GPUMask.h"
#include "CGIResource.h"
#include "Runtime/Math/IntPoint.h"
#include "Runtime/Math/IntVector.h"
#include "Runtime/Misc/TypeHash.h"

namespace Alice
{
	/** Descriptor used to create a texture resource */
	struct CGITextureDesc
	{
		/** Texture flags passed on to RHI texture. */
		ETextureCreateFlags Flags = ETextureCreateFlags::None;
		/** Clear value to use when fast-clearing the texture. */
		FClearValueBinding ClearValue;
		/* A mask representing which GPUs to create the resource on, in a multi-GPU system. */
		GPUMask GPUMask = GPUMask::All();
		/** Platform-specific additional data. Used for offline processed textures on some platforms. */
		uint32 ExtData = 0;
		/** Extent of the texture in x and y. */
		IntPoint Extent = IntPoint(1, 1);
		/** Depth of the texture if the dimension is 3D. */
		uint16 Depth = 1;
		/** The number of array elements in the texture. (Keep at 1 if dimension is 3D). */
		uint16 ArraySize = 1;
		/** Number of mips in the texture mip-map chain. */
		uint8 NumMips = 1;
		/** Number of samples in the texture. >1 for MSAA. */
		uint8 NumSamples = 1;
		/** Texture dimension to use when creating the RHI texture. */
		ETextureDimension Dimension = ETextureDimension::Texture2D;
		/** Pixel format used to create RHI texture. */
		EPixelFormat Format = EPixelFormat::PF_Unknown;
		/** Texture format used when creating the UAV. PF_Unknown means to use the default one (same as Format). */
		EPixelFormat UAVFormat = EPixelFormat::PF_Unknown;
		/** Resource memory percentage which should be allocated onto fast VRAM (hint-only). (encoding into 8bits, 0..255 -> 0%..100%) */
		uint8 FastVRAMPercentage = 0xFF;
		CGITextureDesc() = default;
		CGITextureDesc(const CGITextureDesc& Other)
		{
			*this = Other;
		}
		CGITextureDesc(ETextureDimension InDimension)
			: Dimension(InDimension)
		{}

		CGITextureDesc(
			ETextureDimension   InDimension
			, ETextureCreateFlags InFlags
			, EPixelFormat        InFormat
			, FClearValueBinding  InClearValue
			, IntPoint            InExtent
			, uint16              InDepth
			, uint16              InArraySize
			, uint8               InNumMips
			, uint8               InNumSamples
			, uint32              InExtData
			)
			: Flags     (InFlags     )
			, ClearValue(InClearValue)
			, ExtData   (InExtData   )
			, Extent    (InExtent    )
			, Depth     (InDepth     )
			, ArraySize (InArraySize )
			, NumMips   (InNumMips   )
			, NumSamples(InNumSamples)
			, Dimension (InDimension )
			, Format    (InFormat    )
		{}

		bool operator == (const CGITextureDesc& Other) const
		{
			return Dimension  == Other.Dimension
				&& Flags      == Other.Flags
				&& Format     == Other.Format
				&& UAVFormat  == Other.UAVFormat
				&& Extent     == Other.Extent
				&& Depth      == Other.Depth
				&& ArraySize  == Other.ArraySize
				&& NumMips    == Other.NumMips
				&& NumSamples == Other.NumSamples
				&& FastVRAMPercentage == Other.FastVRAMPercentage
				&& ClearValue == Other.ClearValue
				&& ExtData    == Other.ExtData;
		}

		bool operator != (const CGITextureDesc& Other) const
		{
			return !(*this == Other);
		}

		CGITextureDesc& operator=(const CGITextureDesc& Other)
		{
			Dimension			= Other.Dimension;
			Flags				= Other.Flags;
			Format				= Other.Format;
			UAVFormat			= Other.UAVFormat;
			Extent				= Other.Extent;
			Depth				= Other.Depth;
			ArraySize			= Other.ArraySize;
			NumMips				= Other.NumMips;
			NumSamples			= Other.NumSamples;
			ClearValue			= Other.ClearValue;
			ExtData				= Other.ExtData;
			FastVRAMPercentage	= Other.FastVRAMPercentage;

			return *this;
		}
		bool IsTexture2D() const
		{
			return Dimension == ETextureDimension::Texture2D || Dimension == ETextureDimension::Texture2DArray;
		}
		bool IsTexture3D() const
		{
			return Dimension == ETextureDimension::Texture3D;
		}
		bool IsTextureCube() const
		{
			return Dimension == ETextureDimension::TextureCube || Dimension == ETextureDimension::TextureCubeArray;
		}
		bool IsTextureArray() const
		{
			return Dimension == ETextureDimension::Texture2DArray || Dimension == ETextureDimension::TextureCubeArray;
		}

		bool IsMipChain() const
		{
			return NumMips > 1;
		}

		bool IsMultisample() const
		{
			return NumSamples > 1;
		}

		IntVector GetSize() const
		{
			return IntVector(Extent.X, Extent.Y, Depth);
		}

		void Reset()
		{
			// Usually we don't want to propagate MSAA samples.
			NumSamples = 1;

			// Remove UAV flag for textures that don't need it (some formats are incompatible).
			Flags |= ETextureCreateFlags::RenderTargetable;
			Flags &= ~(ETextureCreateFlags::UAV | ETextureCreateFlags::ResolveTargetable | ETextureCreateFlags::DepthStencilResolveTarget | ETextureCreateFlags::Memoryless);
		}

		/** Returns whether this descriptor conforms to requirements. */
		bool IsValid() const
		{
			return CGITextureDesc::Validate(*this, /* Name = */ TEXT(""), /* bFatal = */ false);
		}
		/** Check the validity. */
		static bool CheckValidity(const CGITextureDesc& Desc, const TCHAR* Name)
		{
			return CGITextureDesc::Validate(Desc, Name, /* bFatal = */ true);
		}

		/**
		 * Returns an estimated total memory size the described texture will occupy in GPU memory.
		 * This is an estimate because it only considers the dimensions / format etc of the texture, 
		 * not any specifics about platform texture layout.
		 * 
		 * To get a true measure of a texture resource for the current running platform RHI, use RHICalcTexturePlatformSize().
		 * 
		 * @param FirstMipIndex - the index of the most detailed mip to consider in the memory size calculation. Must be < NumMips and <= LastMipIndex.
		 * @param LastMipIndex  - the index of the least detailed mip to consider in the memory size calculation. Must be < NumMips and >= FirstMipIndex.
		 */
		uint64 CalcMemorySizeEstimate(uint32 FirstMipIndex, uint32 LastMipIndex) const;

		uint64 CalcMemorySizeEstimate(uint32 FirstMipIndex = 0) const
		{
			return CalcMemorySizeEstimate(FirstMipIndex, NumMips - 1);
		}

	private:
		static bool Validate(const CGITextureDesc& Desc, const TCHAR* Name, bool bFatal);
	};
	struct CGITextureCreateDesc : public CGITextureDesc
	{
		static CGITextureCreateDesc Create(const TCHAR* InDebugName, ETextureDimension InDimension)
		{
			return CGITextureCreateDesc(InDimension);
		}

		static CGITextureCreateDesc Create2D(const TCHAR* InDebugName)
		{
			return CGITextureCreateDesc(ETextureDimension::Texture2D);
		}

		static CGITextureCreateDesc Create2DArray(const TCHAR* InDebugName)
		{
			return CGITextureCreateDesc(ETextureDimension::Texture2DArray);
		}

		static CGITextureCreateDesc Create3D(const TCHAR* InDebugName)
		{
			return CGITextureCreateDesc(ETextureDimension::Texture3D);
		}

		static CGITextureCreateDesc CreateCube(const TCHAR* InDebugName)
		{
			return CGITextureCreateDesc(ETextureDimension::TextureCube);
		}

		static CGITextureCreateDesc CreateCubeArray(const TCHAR* InDebugName)
		{
			return CGITextureCreateDesc(ETextureDimension::TextureCubeArray);
		}

		static CGITextureCreateDesc Create2D(const TCHAR* DebugName, IntPoint Size, EPixelFormat Format)
		{
			return Create2D(DebugName)
				.SetExtent(Size)
				.SetFormat(Format);
		}

		static CGITextureCreateDesc Create2D(const TCHAR* DebugName, int32 SizeX, int32 SizeY, EPixelFormat Format)
		{
			return Create2D(DebugName)
				.SetExtent(SizeX, SizeY)
				.SetFormat(Format);
		}

		static CGITextureCreateDesc Create2DArray(const TCHAR* DebugName, IntPoint Size, uint16 ArraySize, EPixelFormat Format)
		{
			return Create2DArray(DebugName)
				.SetExtent(Size)
				.SetFormat(Format)
				.SetArraySize((uint16)ArraySize);
		}

		static CGITextureCreateDesc Create2DArray(const TCHAR* DebugName, int32 SizeX, int32 SizeY, int32 ArraySize, EPixelFormat Format)
		{
			return Create2DArray(DebugName)
				.SetExtent(SizeX, SizeY)
				.SetFormat(Format)
				.SetArraySize((uint16)ArraySize);
		}

		static CGITextureCreateDesc Create3D(const TCHAR* DebugName, IntVector Size, EPixelFormat Format)
		{
			return Create3D(DebugName)
				.SetExtent(Size.X, Size.Y)
				.SetDepth((uint16)Size.Z)
				.SetFormat(Format);
		}

		static CGITextureCreateDesc Create3D(const TCHAR* DebugName, int32 SizeX, int32 SizeY, int32 SizeZ, EPixelFormat Format)
		{
			return Create3D(DebugName)
				.SetExtent(SizeX, SizeY)
				.SetDepth((uint16)SizeZ)
				.SetFormat(Format);
		}

		static CGITextureCreateDesc CreateCube(const TCHAR* DebugName, uint32 Size, EPixelFormat Format)
		{
			return CreateCube(DebugName)
				.SetExtent(Size)
				.SetFormat(Format);
		}

		static CGITextureCreateDesc CreateCubeArray(const TCHAR* DebugName, uint32 Size, uint16 ArraySize, EPixelFormat Format)
		{
			return CreateCubeArray(DebugName)
				.SetExtent(Size)
				.SetFormat(Format)
				.SetArraySize((uint16)ArraySize);
		}

		CGITextureCreateDesc() = default;

		// Constructor with minimal argument set. Name and dimension are always required.
		CGITextureCreateDesc(ETextureDimension InDimension)
			: CGITextureDesc(InDimension)
		{
		}
		CGITextureCreateDesc& SetFlags(ETextureCreateFlags InFlags)               { Flags = InFlags;                          return *this; }
		CGITextureCreateDesc& AddFlags(ETextureCreateFlags InFlags)               { Flags |= InFlags;                         return *this; }
		CGITextureCreateDesc& SetClearValue(FClearValueBinding InClearValue)      { ClearValue = InClearValue;                return *this; }
		CGITextureCreateDesc& SetExtData(uint32 InExtData)                        { ExtData = InExtData;                      return *this; }
		CGITextureCreateDesc& SetExtent(const IntPoint& InExtent)                 { Extent = InExtent;                        return *this; }
		CGITextureCreateDesc& SetExtent(int32 InExtentX, int32 InExtentY)         { Extent = IntPoint(InExtentX, InExtentY); return *this; }
		CGITextureCreateDesc& SetExtent(uint32 InExtent)                          { Extent = IntPoint(InExtent);             return *this; }
		CGITextureCreateDesc& SetDepth(uint16 InDepth)                            { Depth = InDepth;                          return *this; }
		CGITextureCreateDesc& SetArraySize(uint16 InArraySize)                    { ArraySize = InArraySize;                  return *this; }
		CGITextureCreateDesc& SetNumMips(uint8 InNumMips)                         { NumMips = InNumMips;                      return *this; }
		CGITextureCreateDesc& SetNumSamples(uint8 InNumSamples)                   { NumSamples = InNumSamples;                return *this; }
		CGITextureCreateDesc& SetDimension(ETextureDimension InDimension)         { Dimension = InDimension;                  return *this; }
		CGITextureCreateDesc& SetFormat(EPixelFormat InFormat)                    { Format = InFormat;                        return *this; }
		CGITextureCreateDesc& SetUAVFormat(EPixelFormat InUAVFormat)              { UAVFormat = InUAVFormat;                  return *this; }
		CGITextureCreateDesc& SetInitialState(ECGIAccess InInitialState)          { InitialState = InInitialState;            return *this; }
		CGITextureCreateDesc& DetermineInititialState()                           { if (InitialState == ECGIAccess::Unknown) InitialState = CGIGetDefaultResourceState(Flags, false); return *this; }
		CGITextureCreateDesc& SetFastVRAMPercentage(float In)                     { FastVRAMPercentage = uint8(MathUtils::Clamp(In, 0.f, 1.0f) * 0xFF); return *this; }
		CGITextureCreateDesc& SetClassName(const std::string& InClassName)		  { ClassName = InClassName;				   return *this; }
		CGITextureCreateDesc& SetOwnerName(const std::string& InOwnerName)		  { OwnerName = InOwnerName;                  return *this; }

		/* The RHI access state that the resource will be created in. */
		ECGIAccess InitialState = ECGIAccess::Unknown;

		std::string ClassName;	// The owner class of FRHITexture used for Insight asset metadata tracing
		std::string OwnerName;	// The owner name used for Insight asset metadata tracing
	};
	
	class CGITexture : public CGIViewableResource{
	public:
		CGITextureDesc TextureDesc;
		CGITexture(ECGIResourceType inResourceType):CGIViewableResource(inResourceType,ECGIAccess::Unknown){}
		CGITexture(const CGITextureCreateDesc& InDesc);
		virtual const CGITextureDesc& GetDesc() const { return TextureDesc; }
		uint32 GetNumMips() const { return GetDesc().NumMips; }
	};
}

template<>
struct std::hash<Alice::CGITextureDesc>
{
    size_t  operator()(const Alice::CGITextureDesc& inCGITextureDesc) const noexcept
    {
        uint32 Hash = GetTypeHash(inCGITextureDesc.Dimension);
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.Flags));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.Format));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.UAVFormat));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.Extent));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.Depth));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.ArraySize));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.NumMips));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.NumSamples));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.FastVRAMPercentage));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.ClearValue));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.ExtData));
        Hash = HashCombine(Hash, GetTypeHash(inCGITextureDesc.GPUMask.GetNative()));
        return Hash;
    }
};